/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.web.model;

import com.jb.faces.context.FacesContext;
import com.jb.faces.el.AttributeEvaluator;
import com.jb.framework.Scope;
import com.jb.framework.ScopedVariable;
import com.jb.util.ClassUtil;
import com.jb.web.bean.inspector.OptionPropertyEditor;
import com.jb.web.context.WebContext;
import com.jb.web.renderer.RenderKitImpl;
import com.jb.web.renderer.VariableRenderer;
import com.jb.web.session.SessionEvent;
import com.jb.web.session.SessionListener;

import javax.servlet.jsp.el.ELException;


/**
 * Variable is the component wrapper for managed beans. It can be set to run on
 * server side or client side. The property values are interpreted based on
 * the fact if it is a server side or client side managed bean.
 * <p>Scope defines where to store a newly created bean and how these variable are
 * shared.
 * if value is session, a session attribue will be created with key as the client
 * id of this component.
 * <p> Other possible values for the scope are:
 * <ul>
 * <li>page</li>
 * <li>request</li>
 * <li>application</li>
 * </ul>
 *
 * OnInitialize is an event handler that can be assigned to this component so
 * that when the component is first created, the handler is called so that
 * custom logic can be called to initialized the bean.
 *
 * @author Tim Wang
 * @version 1.0
 */
public class Variable extends AbstractVariable implements SessionListener , ScopedVariable{
    static final long serialVersionUID = 123123L;

    static {
        RenderKitImpl.registerRenderer(Variable.class, VariableRenderer.class);
        publish(Variable.class, GENERAL, "variableType");
        publish(Variable.class, GENERAL, "initialValue");
        publish(Variable.class, GENERAL, "onInitialize");
        publish(Variable.class, GENERAL, "phase", new OptionPropertyEditor("static:0,DECODE;1,MODEL;2,VIEW;"));
        publish(Variable.class, GENERAL, "cacheKey");
    }

    private Object valueObject;
    private String cacheKey;
    private String initialValue;
    private String variableType = null;
    private boolean _initialized = false;

    //phase to evalute the varaible
    //0 when decoding
    //1 when update model
    //2 when update view
    private int phase = 1;
    private Scope myScope;
    
    public Scope getMyScope() {
        return myScope;
    }
    public void setCacheKey(String cacheKey) {
        this.cacheKey = cacheKey;
        if (getFacesContext() != null) {
            WebContext ctx = (WebContext)getFacesContext();
            ctx.getSession().removeAttribute(this.cacheKey);
            ctx.getSession().addAttribute(this.cacheKey, this.valueObject);
        }
    }

    public String getCacheKey() {
        return cacheKey;
    }

    public void setInitialValue(String initialValue) {
        this.initialValue = initialValue;
    }

    public String getInitialValue() {
        return initialValue;
    }

    public void setPhase(int phase) {
        this._initialized = false;
        this.phase = phase;
    }

    public int getPhase() {
        return phase;
    }

    /**
     * Returns string value for this variable
     */
    public String getValueAsString() {
        return (valueObject == null) ? "" : ("" + valueObject);
    }

    public void setValueObject(Object valueObject) {
        this.valueObject = valueObject;
        if (getFacesContext() != null) {
            WebContext pCtx = (WebContext)getFacesContext();
            this.updateScope(pCtx);
        }
    }

    public Object getValueObject() {
        if (!_initialized && (getFacesContext() != null)) {
            this.evaluate(getFacesContext());
        }
        return valueObject;
    }

    public void setVariableType(String variableType) {
        if(variableType!=null && !variableType.equals(this.variableType))
        {
            this.variableType = variableType;
            this.valueObject = 0;
            this._initialized = false;
        }
    }

    public String getVariableType() {
        return variableType;
    }

    public void processDecodes(FacesContext ctx) {
        if (phase == 0) {
            evaluate(ctx);
        }
        super.processDecodes(ctx);
    }

    public void onLogin(WebContext pCtx, SessionEvent event) {
        valueObject = null;
        _initialized = false;
    }

    public void onLogout(WebContext pCtx, SessionEvent event) {
        valueObject = null;
        _initialized = false;
    }

    /**
     * Called by lifecycle manager at time when decode is needed.
     *
     * @param pCtx DOCUMENT ME!
     */
    public void updateView(WebContext pCtx) {
        if (phase == 2) {
            evaluate(pCtx);
        }
    }

    private void updateScope(WebContext pCtx) {
        String ck = cacheKey==null||cacheKey.length()==0?getClientId():cacheKey;
        if ("session".equals(getScope()) && (valueObject != null)) {
            if (ck != null) {
                pCtx.getSession().addAttribute(ck, valueObject);
            }
            pCtx.getSession().addOnLoginListener(this);
            pCtx.getSession().addOnLogoutListener(this);
        }

        if ("request".equalsIgnoreCase(getScope()) && (valueObject != null)) {
            if (ck != null) {
                pCtx.addParameter(ck, valueObject);
            }
        }
    }
    private static String RUN_ON_CLIENT="client";
    
    protected void evaluate(FacesContext webContext) {
        if(RUN_ON_CLIENT.equalsIgnoreCase(runAt))
            return;
        
        if ("request".equals(getScope())) {
            valueObject = null;
        }

        WebContext ctx = (WebContext)webContext;
        //if value object is null, 
        if (!_initialized || "request".equals(getScope())) {
            try {
                //if variable type is not null,
                Class variableClass = null;
                if (variableType != null && variableType.length()>0)
                    variableClass = ClassUtil.getClass(variableType);

                if(initialValue !=null && initialValue.length()>0)
                    valueObject = AttributeEvaluator.evaluate(ctx, this, initialValue, variableClass);
                else if(variableClass!=null)
                    valueObject = variableClass.newInstance();

                if (getOnInitialize() != null) {
                    handleEvent(ctx, getOnInitialize());
                }
                _initialized = true;
            } catch (ELException e) {
                ctx.addErrorMessage(this, "Expression error " + this.getVariableType());
            }catch (Exception e) {
                ctx.addErrorMessage(this, "Class not found " + this.getVariableType() + " : "+e);
                if(log.isErrorEnabled())
                    log.error(this, e);
            }
        }
        updateScope(ctx);
    }

    protected boolean updateModel(FacesContext ctx) {
        if (phase == 1) {
            evaluate(ctx);
        }
        return super.updateModel(ctx);
    }
}
